/**
 * Copyright 2018 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.socket;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;

import javax.net.SocketFactory;

import com.jianggujin.socket.util.JIOUtils;

/**
 * 套接字客户端
 * 
 * @author jianggujin
 *
 */
public class JSocketClient implements Closeable {
   /** 一行结束标志 */
   public static final String NETASCII_EOL = "\r\n";

   /** 默认套接字工厂 */
   private static final SocketFactory DEFAULT_SOCKET_FACTORY = SocketFactory.getDefault();

   /** 已经打开的套接字的超时时间 */
   protected int timeout;

   /** 用于连接的套接字对象 **/
   protected Socket socket;

   /** 默认端口 */
   protected int defaultPort;

   /** 套接字输入流 */
   protected InputStream input;

   /** 套接字输出流 */
   protected OutputStream output;

   /** 用于创建套接字的工厂 */
   protected SocketFactory socketFactory;

   /** 默认套接字连接超时时间，0为无限的 */
   private static final int DEFAULT_CONNECT_TIMEOUT = 0;
   /** 连接超时时间 */
   protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;

   /** 接受缓冲区大小 */
   private int receiveBufferSize = -1;

   /** 发送缓冲区大小 */
   private int sendBufferSize = -1;

   /** 连接时使用的代理 */
   private Proxy connProxy;

   /** 字符集 */
   // private Charset charset = Charset.defaultCharset();
   /** 主机名称 */
   protected String hostname;

   public JSocketClient() {
      socket = null;
      hostname = null;
      input = null;
      output = null;
      timeout = 0;
      defaultPort = 0;
      socketFactory = DEFAULT_SOCKET_FACTORY;
   }

   /**
    * 用于对连接的立即设置，当套接字练级成功后会执行本方法
    * 
    * @throws IOException
    */
   protected void connectAction() throws IOException {
      socket.setSoTimeout(timeout);
      input = socket.getInputStream();
      output = socket.getOutputStream();
   }

   /**
    * 连接
    * 
    * @param host
    * @param port
    * @throws SocketException
    * @throws IOException
    */
   public void connect(InetAddress host, int port) throws SocketException, IOException {
      hostname = null;
      _connect(host, port, null, -1);
   }

   /**
    * 连接
    * 
    * @param hostname
    * @param port
    * @throws SocketException
    * @throws IOException
    */
   public void connect(String hostname, int port) throws SocketException, IOException {
      this.hostname = hostname;
      _connect(InetAddress.getByName(hostname), port, null, -1);
   }

   /**
    * 连接
    * 
    * @param host
    * @param port
    * @param localAddr
    * @param localPort
    * @throws SocketException
    * @throws IOException
    */
   public void connect(InetAddress host, int port, InetAddress localAddr, int localPort)
         throws SocketException, IOException {
      hostname = null;
      _connect(host, port, localAddr, localPort);
   }

   /**
    * 连接辅助方法
    * 
    * @param host
    * @param port
    * @param localAddr
    * @param localPort
    * @throws SocketException
    * @throws IOException
    */
   private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort)
         throws SocketException, IOException {
      socket = socketFactory.createSocket();
      if (receiveBufferSize != -1) {
         socket.setReceiveBufferSize(receiveBufferSize);
      }
      if (sendBufferSize != -1) {
         socket.setSendBufferSize(sendBufferSize);
      }
      if (localAddr != null) {
         socket.bind(new InetSocketAddress(localAddr, localPort));
      }
      socket.connect(new InetSocketAddress(host, port), connectTimeout);
      connectAction();
   }

   /**
    * 连接
    * 
    * @param hostname
    * @param port
    * @param localAddr
    * @param localPort
    * @throws SocketException
    * @throws IOException
    */
   public void connect(String hostname, int port, InetAddress localAddr, int localPort)
         throws SocketException, IOException {
      this.hostname = hostname;
      _connect(InetAddress.getByName(hostname), port, localAddr, localPort);
   }

   /**
    * 连接
    * 
    * @param host
    * @throws SocketException
    * @throws IOException
    */
   public void connect(InetAddress host) throws SocketException, IOException {
      hostname = null;
      connect(host, defaultPort);
   }

   /**
    * 连接
    * 
    * @param hostname
    * @throws SocketException
    * @throws IOException
    */
   public void connect(String hostname) throws SocketException, IOException {
      connect(hostname, defaultPort);
   }

   /**
    * 断开连接
    * 
    * @throws IOException
    */
   public void disconnect() throws IOException {
      JIOUtils.safeClose(socket);
      JIOUtils.safeClose(input);
      JIOUtils.safeClose(output);
      socket = null;
      hostname = null;
      input = null;
      output = null;
   }

   /**
    * 关闭连接，等价于{@link #disconnect()}
    */
   @Override
   public void close() throws IOException {
      this.disconnect();
   }

   /**
    * 判断是否已连接
    * 
    * @return
    */
   public boolean isConnected() {
      if (socket == null) {
         return false;
      }

      return socket.isConnected();
   }

   /**
    * 判断连接是否可用
    */
   public boolean isAvailable() {
      if (isConnected()) {
         try {
            if (socket.getInetAddress() == null) {
               return false;
            }
            if (socket.getPort() == 0) {
               return false;
            }
            if (socket.getRemoteSocketAddress() == null) {
               return false;
            }
            if (socket.isClosed()) {
               return false;
            }
            /*
             * these aren't exact checks (a Socket can be half-open), but since
             * we usually require two-way data transfer, we check these here
             * too:
             */
            if (socket.isInputShutdown()) {
               return false;
            }
            if (socket.isOutputShutdown()) {
               return false;
            }
            /* ignore the result, catch exceptions: */
            socket.getInputStream();
            socket.getOutputStream();
         } catch (IOException ioex) {
            return false;
         }
         return true;
      } else {
         return false;
      }
   }

   /**
    * 设置默认端口
    * 
    * @param port
    */
   public void setDefaultPort(int port) {
      defaultPort = port;
   }

   /**
    * 获得默认端口
    * 
    * @return
    */
   public int getDefaultPort() {
      return defaultPort;
   }

   /**
    * 设置默认超时时间
    * 
    * @param timeout
    */
   public void setDefaultTimeout(int timeout) {
      this.timeout = timeout;
   }

   /**
    * 获得默认超时时间
    * 
    * @return
    */
   public int getDefaultTimeout() {
      return timeout;
   }

   /**
    * 设置已打开的连接超时时间
    * 
    * @param timeout
    * @throws SocketException
    */
   public void setSoTimeout(int timeout) throws SocketException {
      socket.setSoTimeout(timeout);
   }

   /**
    * 获得已打开的连接超时时间
    * 
    * @return
    * @throws SocketException
    */
   public int getSoTimeout() throws SocketException {
      return socket.getSoTimeout();
   }

   /**
    * 设置套接字发送缓冲大小
    * 
    * @param size
    * @throws SocketException
    */
   public void setSendBufferSize(int size) throws SocketException {
      sendBufferSize = size;
   }

   /**
    * 获得套接字发送缓冲大小
    * 
    * @return
    */
   protected int getSendBufferSize() {
      return sendBufferSize;
   }

   /**
    * 设置套接字接收缓冲大小
    * 
    * @param size
    * @throws SocketException
    */
   public void setReceiveBufferSize(int size) throws SocketException {
      receiveBufferSize = size;
   }

   /**
    * 获得套接字接收缓冲大小
    * 
    * @return
    */
   protected int getReceiveBufferSize() {
      return receiveBufferSize;
   }

   /**
    * 启用/禁用 TCP_NODELAY（启用/禁用 Nagle 算法）
    * 
    * @param on
    * @throws SocketException
    */
   public void setTcpNoDelay(boolean on) throws SocketException {
      socket.setTcpNoDelay(on);
   }

   /**
    * 测试是否启用 TCP_NODELAY
    * 
    * @return
    * @throws SocketException
    */
   public boolean getTcpNoDelay() throws SocketException {
      return socket.getTcpNoDelay();
   }

   /**
    * 启用/禁用 SO_KEEPALIVE
    * 
    * @param keepAlive
    * @throws SocketException
    */
   public void setKeepAlive(boolean keepAlive) throws SocketException {
      socket.setKeepAlive(keepAlive);
   }

   /**
    * 测试是否启用 SO_KEEPALIVE
    * 
    * @return
    * @throws SocketException
    */
   public boolean getKeepAlive() throws SocketException {
      return socket.getKeepAlive();
   }

   /**
    * 启用/禁用具有指定逗留时间（以秒为单位）的 SO_LINGER
    * 
    * @param on
    * @param val
    * @throws SocketException
    */
   public void setSoLinger(boolean on, int val) throws SocketException {
      socket.setSoLinger(on, val);
   }

   /**
    * 获得SO_LINGER 的设置
    * 
    * @return
    * @throws SocketException
    */
   public int getSoLinger() throws SocketException {
      return socket.getSoLinger();
   }

   /**
    * 获得套接字绑定到的本地端口
    * 
    * @return
    */
   public int getLocalPort() {
      return socket.getLocalPort();
   }

   /**
    * 获取套接字绑定的本地地址
    * 
    * @return
    */
   public InetAddress getLocalAddress() {
      return socket.getLocalAddress();
   }

   /**
    * 获得套接字连接到的远程端口
    * 
    * @return
    */
   public int getRemotePort() {
      return socket.getPort();
   }

   /**
    * 获得套接字连接的地址
    * 
    * @return
    */
   public InetAddress getRemoteAddress() {
      return socket.getInetAddress();
   }

   /**
    * 设置套接字工厂
    * 
    * @param factory
    */
   public void setSocketFactory(SocketFactory factory) {
      if (factory == null) {
         socketFactory = DEFAULT_SOCKET_FACTORY;
      } else {
         socketFactory = factory;
      }
      // 重新设置套接字工厂，需要重置代理对象
      connProxy = null;
   }

   /**
    * 设置连接超时时间
    * 
    * @param connectTimeout
    */
   public void setConnectTimeout(int connectTimeout) {
      this.connectTimeout = connectTimeout;
   }

   /**
    * 获得连接超时时间
    * 
    * @return
    */
   public int getConnectTimeout() {
      return connectTimeout;
   }

   /**
    * 设置代理对象
    * 
    * @param proxy
    */
   public void setProxy(Proxy proxy) {
      setSocketFactory(new JDefaultSocketFactory(proxy));
      connProxy = proxy;
   }

   /**
    * 获得代理对象
    * 
    * @return
    */
   public Proxy getProxy() {
      return connProxy;
   }

   public InputStream getInputStream() {
      return input;
   }

   public OutputStream getOutputStream() {
      return output;
   }

}
